﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SqlClient;
using System.IO;

namespace GenerateEntityClasses
{
    class EntityClass
    { 
        public string Name;
        public string TableName;
        public List<DataMemberClass> DataMembers = new List<DataMemberClass>();
    }

    class DataMemberClass
    {
        public string Name;
        public string DataType;
        public string DBType;
        public int Length;
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            ProgressBar progress = new ProgressBar(50);
            List<EntityClass> entityClassList = new List<EntityClass>();

            Console.WriteLine("*********************************************");
            Console.WriteLine("*        Entity Class Generation Tool       *");
            Console.WriteLine("*********************************************" + Environment.NewLine);
            Console.Write("Server: ");
            string serverName = Console.ReadLine();
            Console.Write("Database: ");
            string databaseName = Console.ReadLine();
            Console.Write("User Name: ");
            string userName = Console.ReadLine();
            Console.Write("Password: ");
            string password = Console.ReadLine();
            string connectionString = string.Format("Data Source={0};Initial Catalog={1};" +
            "Persist Security Info=True;User ID={2};Password={3}", 
            serverName, databaseName, userName, password);

            Console.Write("Table Name: ");
            string tableName = Console.ReadLine();

            int constantID = 0;
            string entityName = string.Empty;
            string privateMember = string.Empty;
            string queryString = string.Empty;
            if (string.IsNullOrEmpty(tableName))
            {
                queryString = @"SELECT t1.name
                                    FROM sysobjects t1
                                    INNER JOIN sysindexes t2
                                    ON t1.id = t2.id
                                    WHERE t2.indid <= 1
                                    AND OBJECTPROPERTY(t1.id,'IsUserTable') = 1";

                using (SqlConnection connection = new SqlConnection(connectionString))
                {
                    SqlCommand command = new SqlCommand(queryString, connection);
                    connection.Open();

                    SqlDataReader reader = command.ExecuteReader();

                    if (reader.HasRows)
                    {
                        Console.WriteLine(Environment.NewLine + "*********************************************");
                        Console.WriteLine("*                 TableNames                *");
                        Console.WriteLine("*********************************************");
                        // Call Read before accessing data.
                        while (reader.Read())
                        {
                            Console.WriteLine(String.Format("{0}", reader[0]));
                            entityClassList.Add(new EntityClass() { 
                                Name = reader[0].ToString(),
                                TableName = "dbo." + reader[0].ToString(),
                            });
                        }
                    }

                    // Call Close when done reading.
                    reader.Close();
                }
            }
            else
                entityClassList.Add(new EntityClass() { Name = tableName, });

            if (entityClassList.Count() > 0)
            {
                using (SqlConnection connection = new SqlConnection(connectionString))
                {
                    connection.Open();
                    SqlCommand command = connection.CreateCommand();
                    command.CommandType = System.Data.CommandType.Text;

                    foreach (EntityClass entity in entityClassList)
                    {
                        /*select   Name, type_name(xtype) DataType,length from syscolumns where id =
                          object_id('dbo.Address') order by colid */

                        Console.WriteLine(Environment.NewLine + "*********************************************");
                        Console.WriteLine(string.Format("*        {0}       *", entity.Name));
                        Console.WriteLine("*********************************************");
                        queryString = string.Format("select Name, xtype DataType, type_name(xtype) DBType, length from syscolumns " +
                            " where id = object_id('{0}') order by colid", entity.Name);

                        command.CommandText = queryString;

                        SqlDataReader reader = command.ExecuteReader();

                        if (reader.HasRows)
                        {
                            // Call Read before accessing data.
                            while (reader.Read())
                            {
                                Console.WriteLine(String.Format("{0}", reader[0]));
                                DataMemberClass dataMember = new DataMemberClass()
                                {
                                    Name = reader[0].ToString(),
                                    DBType = reader[2].ToString(),
                                    Length = int.Parse(reader[3].ToString())
                                };

                                switch (int.Parse(reader[1].ToString()))
                                {
                                    case 127: dataMember.DataType = "long"; break;
                                    case 56: dataMember.DataType = "int"; break;
                                    case 167: dataMember.DataType = "string"; break;
                                    case 62: dataMember.DataType = "double"; break;
                                    case 104: dataMember.DataType = "bool"; break;
                                    case 61: dataMember.DataType = "DateTime"; break;
                                    default: dataMember.DataType = "string"; break;
                                };
                                entity.DataMembers.Add(dataMember);
                            }
                        }

                        // Call Close when done reading.
                        reader.Close();
                    }
                }

                Console.WriteLine(Environment.NewLine + Environment.NewLine + "*********************************************");
                Console.Write("Name Space: ");
                string nameSpace = Console.ReadLine();
                Console.WriteLine("*********************************************" + Environment.NewLine);
                double count = 1;
                foreach (var item in entityClassList)
                {
                    progress.Update(count++ / entityClassList.Count());
                    entityName = item.Name;
                    tableName = item.TableName;
                    var rawDataMembers = item.DataMembers;

                    StringBuilder privateDataMemberRegion = new StringBuilder();
                    privateDataMemberRegion.AppendLine();
                    privateDataMemberRegion.AppendLine("\t\t# region Private Data Members");
                    privateDataMemberRegion.AppendLine();

                    StringBuilder privateStaticDataMemberRegion = new StringBuilder();
                    privateStaticDataMemberRegion.AppendLine();
                    privateStaticDataMemberRegion.AppendLine("\t\t# region Private \"Static\" Data Members");
                    privateStaticDataMemberRegion.AppendLine();
                    privateStaticDataMemberRegion.AppendLine("\t\tprivate static readonly string[] _propertyNames = new string[] {");

                    StringBuilder constantsRegion = new StringBuilder();
                    constantsRegion.AppendLine();
                    constantsRegion.AppendLine("\t\t# region Constants");
                    constantsRegion.AppendLine();
                    constantsRegion.AppendLine("\t\tpublic const int ");

                    StringBuilder privateInstanceDataMember = new StringBuilder();
                    privateInstanceDataMember.AppendLine();
                    privateInstanceDataMember.AppendLine("\t\t# region Private instance data members");
                    privateInstanceDataMember.AppendLine();
                    privateInstanceDataMember.AppendLine("\t\tprivate FieldInfo[] _fieldInfo = new FieldInfo[]{");

                    StringBuilder publicPropertiesRegion = new StringBuilder();
                    publicPropertiesRegion.AppendLine();
                    publicPropertiesRegion.AppendLine("\t\t# region Public Properties");

                    constantID = 0;
                    foreach (var v in rawDataMembers)
                    {
                        if (!string.IsNullOrEmpty(v.Name) && !string.IsNullOrEmpty(v.DataType))
                        {
                            privateMember = " _" + v.Name.Substring(0, 1).ToLower() + v.Name.Substring(1);

                            privateDataMemberRegion.AppendLine("\t\tprivate " + v.DataType + " " + privateMember + ";");

                            privateStaticDataMemberRegion.AppendLine("\t\t\t\"" + v.Name + "\",");

                            constantsRegion.AppendLine("\t\t\tField" + v.Name + "Idx = " + constantID + ",");

                            privateInstanceDataMember.AppendLine("\t\t\tnew FieldInfo(Field" + v.Name + "Idx,FieldType.Primitive),");

                            publicPropertiesRegion.AppendLine();
                            if (!string.IsNullOrEmpty(v.Name) && !string.IsNullOrEmpty(v.DBType))
                                publicPropertiesRegion.AppendLine("\t\t[Column(DBType = \"" + v.DBType + "\")]");
                            publicPropertiesRegion.AppendLine("\t\tpublic " + v.DataType + " " + v.Name + "");
                            publicPropertiesRegion.AppendLine("\t\t{");
                            publicPropertiesRegion.AppendLine("\t\t\tget { return this.GetHelper(_fieldInfo, _propertyNames, Field" + v.Name + "Idx, " + privateMember + "); }");
                            publicPropertiesRegion.AppendLine("\t\t\tset { " + privateMember + " = this.SetValueTypeHelper(_fieldInfo, Field" + v.Name +
                                "Idx, " + privateMember + ", value, " + privateMember + " == value); }");
                            publicPropertiesRegion.AppendLine("\t\t}");

                            constantID++;
                        }
                    }
                    privateDataMemberRegion.AppendLine();
                    privateDataMemberRegion.AppendLine("\t\t# endregion");

                    privateStaticDataMemberRegion.AppendLine("\t\t};");
                    privateStaticDataMemberRegion.AppendLine();
                    privateStaticDataMemberRegion.AppendLine("\t\t# endregion");

                    constantsRegion.Replace(',', ';', constantsRegion.Length - 3, 2);
                    constantsRegion.AppendLine();
                    constantsRegion.AppendLine("\t\t# endregion");

                    privateInstanceDataMember.AppendLine("\t\t};");
                    privateInstanceDataMember.AppendLine();
                    privateInstanceDataMember.AppendLine("\t\t# endregion");

                    publicPropertiesRegion.AppendLine();
                    publicPropertiesRegion.AppendLine("\t\t# endregion");

                    StringBuilder sb = new StringBuilder();
                    sb.AppendLine("using System;");
                    sb.AppendLine("using System.Collections.Generic;");
                    sb.AppendLine("using System.Linq;");

                    sb.AppendLine();
                    sb.AppendLine("namespace " + nameSpace);
                    sb.AppendLine("{");
                    if (!string.IsNullOrEmpty(tableName))
                        sb.AppendLine("\t[Table(Name = \"" + tableName + "\")]");
                    sb.AppendLine("\tpublic class " + entityName);
                    sb.AppendLine("\t{");
                    sb.Append(privateDataMemberRegion);
                    sb.Append(privateStaticDataMemberRegion);
                    sb.Append(constantsRegion);
                    sb.Append(privateInstanceDataMember);
                    sb.Append(publicPropertiesRegion);
                    sb.AppendLine("\t}");
                    sb.AppendLine("}");


                    // create a writer and open the file
                    TextWriter tw = new StreamWriter(entityName + ".cs");
                    tw.Write(sb);

                    tw.Close();
                }
            }

            Console.WriteLine(Environment.NewLine + Environment.NewLine + "Press any key to continue.");
            Console.ReadLine();
        }
    }

    public class ProgressBar
    {
        #region Fields

        /// <summary>
        /// The length of the last output
        /// </summary>
        private int lastOutputLength;

        /// <summary>
        /// The maximum length of the progress bar
        /// </summary>
        private int maximumWidth;

        #endregion Fields

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the  class.
        /// </summary>
        /// <param name="maximumWidth">The maximum width of the progress bar.</param>
        public ProgressBar(int maximumWidth)
        {
            this.maximumWidth = maximumWidth;
            this.Show(" [ ");
        }

        #endregion Constructors

        #region Methods

        /// <summary>
        /// Updates the progress bar with the secified percent.
        /// </summary>
        /// <param name="percent">The percent.</param>
        public void Update(double percent)
        {
            // Remove the last state
            string clear = string.Empty.PadRight(
                this.lastOutputLength,
                '\b');
            this.Show(clear);

            // Generate new state
            int width = (int)(percent / 100 * this.maximumWidth);
            int fill = this.maximumWidth - width;
            string output = string.Format(
                "{0}{1} ] {2}%",
                string.Empty.PadLeft(width, '='),
                string.Empty.PadLeft(fill, ' '),
                percent.ToString("0.0"));
            this.Show(output);
            this.lastOutputLength = output.Length;
        }

        /// <summary>
        /// Shows the specified value.
        /// </summary>
        /// <param name="value">The value.</param>
        private void Show(string value)
        {
            System.Console.Write(value);
        }

        #endregion Methods
    }
}
